/*
 * $Id: AbstractGraphTag.java 41 2013-01-08 04:03:56Z rob@patientsknowbest.com $ 
 */
package com.representqueens.web.tags.spark;

/*
 * Copyright 2006 Larry Ogrodnek
 * Copyright 2011 Rob Whelan for Patients Know Best
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.jsp.tagext.TagSupport;

import com.representqueens.spark.BarGraph;
import com.representqueens.spark.DataPoint;
import com.representqueens.spark.Encoder;
import com.representqueens.spark.Encoder.Format;
import com.representqueens.spark.LineGraph;
import com.representqueens.spark.SizeParams;
import com.representqueens.spark.util.StringConversion;
import com.representqueens.util.ColorUtils;
import com.representqueens.util.StringUtils;

/**
 * @author Larry Ogrodnek <larry@cheesesteak.net>
 * @author Rob Whelan <rob@patientsknowbest.com>
 * @version $Revision: 41 $ $Date: 2013-01-08 04:03:56 +0000 (Tue, 08 Jan 2013) $
 */
public abstract class AbstractGraphTag extends TagSupport {
	/** @deprecated replaced by "y" */
	private String name;
	
	private String width;
	private String height;
	private String spacing;
	private String color;
	private String highColor;
	private String lastColor;
	private String background; 
	private String rangeColor;
	private String output;
	private String forceYMin, forceYMax;
	private String forceXMin, forceXMax;
	
	// all this is merged into a single "data" parameter
	private Object y;
    private Object x;
    private Object rangeLow;
    private Object rangeHigh;
    private Object shapeIndex;
    private Object shapeType;
    private Object shapeColor;


	protected String getBarGraphData() throws IOException {
		final BufferedImage image = BarGraph.createGraph(getData(),
				getSizeParams(BarGraph.DEFAULT_SIZE),
				StringConversion.convertColor(this.color, BarGraph.DEFAULT_COLOR));

		return Encoder.encode(image, getOutputFormat());
	}

	protected String getLineGraphData() throws IOException {
		final BufferedImage image = LineGraph.createGraph(getData(),
				getSizeParams(LineGraph.DEFAULT_SIZE),
				StringConversion.convertColor(this.color, LineGraph.DEFAULT_COLOR));

		return Encoder.encode(image, getOutputFormat());
	}

	protected SizeParams getSizeParams(final SizeParams defaults) {
		return new SizeParams(StringConversion.convertInt(this.width, defaults.getWidth()),
				StringConversion.convertInt(this.height, defaults.getHeight()),
				StringConversion.convertInt(this.spacing, defaults.getSpacing()),
				StringConversion.convertDouble(this.forceYMin, null),
				StringConversion.convertDouble(this.forceYMax, null),
				StringConversion.convertDouble(this.forceXMin, null),
				StringConversion.convertDouble(this.forceXMax, null));
	}

	protected List<DataPoint> getData() {
		List<DataPoint> list = new ArrayList<DataPoint>();
		// this is the only required one: y coordinates
		List<Double> yList = StringConversion.extractDoubleList(getY());
		if( yList.isEmpty() )
			yList = StringConversion.extractDoubleList(pageContext.findAttribute(getName()));
		List<Double> xList = StringConversion.extractDoubleList(getX());
		List<Double> rangeLowList = StringConversion.extractDoubleList(getRangeLow());
		List<Double> rangeHighList = StringConversion.extractDoubleList(getRangeHigh());
		List<Integer> shapeIndexList = StringConversion.extractIntegerList(getShapeIndex());
		List<String> shapeTypeList = StringConversion.extractStringList(getShapeType());
		List<String> shapeColorList = StringConversion.extractStringList(getShapeColor());
		
		// handling for convenience: mark the high/last values
		// note: if parseColor fails, these will be silently ignored
		Color highC = ColorUtils.parseColor(getHighColor());
		Color lastC = ColorUtils.parseColor(getLastColor());
		// for simplicity, don't support BOTH high/last color and the shape* lists.
		if( (highC != null || lastC != null) && ! shapeIndexList.isEmpty() ) {
			System.err.println("highColor/lastColor overriding shape* attibutes...");
			shapeIndexList.clear();
			shapeTypeList.clear();
			shapeColorList.clear();
		}

		if( highC != null ) {
			// Old implementation: mark all above average values
			// New: mark just the highest value (more generally useful)
			// though the other version is also possible by using the shape variables
			// (must specify a shape even for a bar graph... TODO figure out cleaner interface?)
			double max = Double.MIN_VALUE;
			for (Double val : yList) {
				max = Math.max(max, val);
			}
			for (int i=0; i<yList.size(); i++) {
				Double val = yList.get(i);
				if( val == max ) {
					shapeIndexList.add(i);
					shapeTypeList.add(DataPoint.Shape.CIRCLE.toString()); // ignored for bar graph
					shapeColorList.add(getHighColor());
				}
			}
		}
		if( lastC != null ) {
			Integer lastX = yList.size()-1;
			// if the last value is a high value already, don't overwrite that
			if( shapeIndexList.isEmpty() || ! shapeIndexList.get(shapeIndexList.size()-1).equals(lastX) ) {
				shapeIndexList.add(lastX);
				shapeTypeList.add(DataPoint.Shape.CIRCLE.toString()); // ignored for bar graph
				shapeColorList.add(getLastColor());
			}
		}
		
		// sanity checks
		if( yList.isEmpty() )
			throw new IllegalArgumentException("no y data found: " + getY() );
		int count = yList.size();
		if( ! xList.isEmpty() && xList.size() != count )
			throw new IllegalArgumentException("x data points: found " + xList.size() + "; y data has " + count);
		if( ! rangeLowList.isEmpty() && rangeLowList.size() != count )
			throw new IllegalArgumentException("range low data points: found " + xList.size() + "; y data has " + count);
		if( ! rangeHighList.isEmpty() && rangeHighList.size() != count )
			throw new IllegalArgumentException("range high data points: found " + xList.size() + "; y data has " + count);
		if( rangeLowList.size() != rangeHighList.size() )
			throw new IllegalArgumentException("must specify both or neither range low and range high: " + rangeLowList.size() +"/"+ rangeHighList.size());
		if( shapeIndexList.size() != shapeTypeList.size() || shapeTypeList.size() != shapeColorList.size() )
			throw new IllegalArgumentException("shape index/type/color lists must all have the same number of values: "
					+ shapeIndexList.size() +"/"+ shapeTypeList.size() +"/"+ shapeColorList.size());

		for( int i=0; i<count; i++ ) {
			DataPoint dp = new DataPoint(yList.get(i));
			if(! xList.isEmpty())
				dp.setX(xList.get(i));
			if(! rangeLowList.isEmpty())
				dp.setRangeLow(rangeLowList.get(i));
			if(! rangeHighList.isEmpty())
				dp.setRangeHigh(rangeHighList.get(i));
			
			int shapeX = Collections.binarySearch(shapeIndexList, i);
			if( shapeX >= 0 ) {
				DataPoint.Shape sh = DataPoint.Shape.fromValue(shapeTypeList.get(shapeX));
				Color c = ColorUtils.parseColor(shapeColorList.get(shapeX));
				if( sh == null )
					sh = DataPoint.Shape.CIRCLE;
				if( c == null )
					c = StringConversion.convertColor(this.color, LineGraph.DEFAULT_COLOR);
				dp.setShape(sh);
				dp.setColor(c);
			}
			list.add(dp);
		}
		
		return list;
	}
	

	protected Format getOutputFormat() {
		if (!StringUtils.isEmpty(this.output)) {
			if ("data".equalsIgnoreCase(this.output)) {
				return Format.DATA;
			}
		}
		return Format.JAVASCRIPT;
	}

	// just getters/setters from here on down...
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getWidth() {
		return width;
	}
	public void setWidth(String width) {
		this.width = width;
	}
	public String getHeight() {
		return height;
	}
	public void setHeight(String height) {
		this.height = height;
	}
	public String getSpacing() {
		return spacing;
	}
	public void setSpacing(String spacing) {
		this.spacing = spacing;
	}
	public String getForceYMin() {
		return forceYMin;
	}
	public void setForceYMin(String forceYMin) {
		this.forceYMin = forceYMin;
	}
	public String getForceYMax() {
		return forceYMax;
	}
	public void setForceYMax(String forceYMax) {
		this.forceYMax = forceYMax;
	}
	public String getForceXMin() {
		return forceXMin;
	}
	public void setForceXMin(String forceXMin) {
		this.forceXMin = forceXMin;
	}
	public String getForceXMax() {
		return forceXMax;
	}
	public void setForceXMax(String forceXMax) {
		this.forceXMax = forceXMax;
	}
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	public String getHighColor() {
		return highColor;
	}
	public void setHighColor(String highColor) {
		this.highColor = highColor;
	}
	public String getLastColor() {
		return lastColor;
	}
	public void setLastColor(String lastColor) {
		this.lastColor = lastColor;
	}
	public String getBackground() {
		return background;
	}
	public void setBackground(String background) {
		this.background = background;
	}
	public String getOutput() {
		return output;
	}
	public void setOutput(String output) {
		this.output = output;
	}
	public Object getY() {
		return y;
	}
	public void setY(Object y) {
		this.y = y;
	}
	public Object getX() {
		return x;
	}
	public void setX(Object x) {
		this.x = x;
	}
	public Object getRangeLow() {
		return rangeLow;
	}
	public void setRangeLow(Object rangeLow) {
		this.rangeLow = rangeLow;
	}
	public Object getRangeHigh() {
		return rangeHigh;
	}
	public void setRangeHigh(Object rangeHigh) {
		this.rangeHigh = rangeHigh;
	}
	public Object getShapeIndex() {
		return shapeIndex;
	}
	public void setShapeIndex(Object shapeIndex) {
		this.shapeIndex = shapeIndex;
	}
	public Object getShapeType() {
		return shapeType;
	}
	public void setShapeType(Object shapeType) {
		this.shapeType = shapeType;
	}
	public Object getShapeColor() {
		return shapeColor;
	}
	public void setShapeColor(Object shapeColor) {
		this.shapeColor = shapeColor;
	}
	public String getRangeColor() {
		return rangeColor;
	}
	public void setRangeColor(String rangeColor) {
		this.rangeColor = rangeColor;
	}
}
